require 'uri'

class SearchController < ApplicationController
  def default_result_size
    5
  end

  # This is the first page shown when accessing ARC scheduler
  # When user searches, it shows the search results
  # GET /index
  # GET /index.xml
  def index
    if params[:query] and params[:query].length > 0:
      query_fields = SearchController.parse(params[:query])
      term = Term.find(:first, :order => "start_date DESC")
      if term
        offset = params[:offset] ? [params[:offset].to_i, 0].max : 0
        limit = params[:limit] ? [[params[:limit].to_i, 25].min, 1].max : 5
        @events = term.query(query_fields, offset, limit)

        @nav_total_events = term.query(query_fields, 0, 10000).length
        @nav_offset = offset
        @nav_limit = limit
      else
        @events = []
      end
      # Maybe store original query in the session on successful retrieval?
      render :action => 'results'
    end
  end

  # PUT /search
  # PUT /search.xml
  def save_class
    error_msg = nil

    selected_lecture = Lecture.find_by_ccn(params[:ccn])

    if not selected_lecture
      error_msg = 'Could not find the specified course'

    # If save=true, then user is adding the lecture
    elsif params[:save] =~ /t(rue)?/i
      working_schedule.add_event(selected_lecture)
    else
      if working_schedule.events.include?(selected_lecture)
        working_schedule.events.delete(selected_lecture)
      else
        error_msg = 'Specified course (CCN:' + selected_lecture.ccn.to_s +
          ') could not be removed because it was not in your schedule'
      end
    end

    response.content_type = Mime::JSON

    # When there was no error, status is true
    r = { :status => (error_msg ? false : true) }
    if error_msg
      r['error'] = error_msg + ', please try again.'
    else
      r['alternative_lectures'] = selected_lecture.alternative_lectures.collect {|lec| lec.ccn}
    end
    render :text=> r.to_json
  end

  private

  # This method parses searching string, and produces a hash
  # that can be fed into Term's query method
  def SearchController.parse(query)
    list_of_words = query.split(/\s+/)
    result = {}

    i = 0
    while i < list_of_words.length do
      current_word = list_of_words[i]

      # If it's a string of digits followed by '-' followed by digits, then it's a time-range.
      if m = current_word.match(/^(\d{1,2}(\:\d{1,2})?([ap]m)?)\-(\d{1,2}(\:\d{1,2})?([ap]m)?)$/i)
        if time_range = self.parse_time_range(m[1], m[4])
          result[:start_time] = time_range[0]
          result[:stop_time] = time_range[1]
        end

      # If it's a string of SuMTuWThFSa (any term can be misssing), then they are days of week
      # This will NOT match an empty given list_of_words is split by /\s+/
      # Let events happen on a subset of the specified days of week.
      # e.g. MWF should allow events happening only on MW.
      # To do that, we specify in which days events are NOT allowed.
      elsif m = current_word.match(/^(Su)?(M)?(Tu)?(W)?(Th)?(F)?(Sa)?(Su)?$/i)
        result[:sunday] = false if (not m[1]) and (not m[8])
        result[:monday] = false if not m[2]
        result[:tuesday] = false if not m[3]
        result[:wednesday] = false if not m[4]
        result[:thursday] = false if not m[5]
        result[:friday] = false if not m[6]
        result[:saturday] = false if not m[7]

      # If it's a string of alphabets followed by at least one digit (such as cs162),
      # Assume it's <department/department abbreviation><course number>
      # Okay even if find_department returned nil
      elsif m = current_word.match(/^([A-Za-z]+)((\d{3,3}-\d{1,2}|\d{1,3})[A-Za-z]*)$/i)
        result[:department] = DepartmentAbbreviation.find_department(m[1])
        result[:course_number] = m[2]

      # If it matches one of instructor names, then so it be
      elsif Instructor.find_by_lastname(current_word)
        result[:last_name] = current_word

      else

        # If above fails, try combining multiple words and match against long department names and locations
        j = i
        while j < list_of_words.length do
          name = list_of_words[i..j].join(' ')
          if current_word.match(/^(\d)+/) and Event.is_valid_location(name)
            result[:location] ||= name
            i = j
            break
          elsif current_word.match(/^[A-Za-z]+/i) and name = DepartmentAbbreviation.find_department(name)
            result[:department] ||= name
            i = j # This statement prevents us from using ".each" instead of while
            break
          end
          j+=1
        end

        # Combining multiple words succeeded.
        if i == j
        # If above failed and digits appear, then it's a course number
        elsif m = current_word.match(/^((\d{3,3}-\d{1,2}|\d{1,3})[A-Za-z]*)$/i)
          result[:course_number] = m[1]
        end

      end

      i+=1
    end

    return result
  end

  # Parse strings for the starting and stopping times,
  # and return a time range, a list of starting and stopping time
  def SearchController.parse_time_range(start_str, stop_str)

    start = self.parse_time(start_str)
    stop = self.parse_time(stop_str)

    # If stoppin time is valid but starting time is ambiguous,
    # then try pm first since it's easy to tell easily when start is too late
    # but it's hard to tell whether start is too early or not.
    if not start and stop
      start = Time.parse(start_str+"pm")
      start = Time.parse(start_str+"am") if start >= stop
    # If both are ambiguous, then try am for starting time.
    # If that's too early (before 5am) then try pm for start
    # (stop must also be in afternoon in this case)
    elsif not start and not stop
      start = Time.parse(start_str+"am")
      if start < Time.parse("5am")
        start = Time.parse(start_str+"pm")
        stop = Time.parse(stop_str+"pm")
      end
    end

    # If stopping time is ambiguous, then try am first since it's easy to tell
    # when stopping time is too early but it's hard to tell if too late.
    # e.g. 9am-10 should be interpreted as 9am-10am rather than 9am-10pm
    if not stop
      stop = Time.parse(stop_str+"am")
      stop = Time.parse(stop_str+"pm") if start >= stop
    end

    return start < stop ? [start, stop] : nil
  end

  # Parse string and return Time  If the string is missing am/pm
  # and the hour is less than or equal to 12, then return nil
  # since it's ambiguous whether it's morning or afternoon
  def SearchController.parse_time(str)
    m = str.match(/^(\d{1,2}(\:\d{1,2})?)([ap]m)?$/i)
    return nil if not m
    return Time.parse(m[1]+":00") if m[1].to_i > 12
    return Time.parse(str) if m[3]
    return nil
  end

end
